# -*- encoding:utf-8 -*-
import re
import math
from sqlite3 import dbapi2 as sqlite

def getwords(doc):
    splitter = re.compile('\\W*')
    # 根据非字母字符进行单词拆分
    words = [s.lower() for s in splitter.split(doc) if len(s) > 2 and len(s) < 20]

    # 单词去重
    return dict([(w, 1) for w in words])

class classifier:
    def __init__(self, getfeatures, filename=None):
        # 统计特征/分类组合的数量
        self.fc = {}
        # 统计每个分类中的文档数量
        self.cc = {}
        self.getfeatures = getfeatures
        self.con = sqlite.connect(filename)

    def __del__(self):
        self.con.close()

    # 添加关于数据库的操作
    def setdb(self):
        self.con.execute('create table if not exists fc(feature,category,count)')
        self.con.execute('create table if not exists cc(category,count)')

    # 增加对特征/分类组合的计数值
    # {'brown': {'good': 1}, 'lazy': {'good': 1}, 'over': ....
    def incf(self, f, cat):
        count = self.fcount(f,cat)
        if count==0:
            self.con.execute('insert into fc values("%s","%s",1)' % (f,cat))
        else :
            self.con.execute('update fc set count=%d where \
                             feature="%s" and category="%s"' % (count+1,f,cat))

    # 增加对某一分类的计数值
    def incc(self, cat):
        count = self.catcount(cat)
        if count==0:
            self.con.execute("insert into cc values('%s',1)" % (cat))
        else :
            self.con.execute("update cc set count=%d where \
                             category='%s'" % (count+1,cat))

    # 某一特征出现于某一分类中的次数
    def fcount(self, f, cat):
        res = self.con.execute("select count from fc where \
                               feature='%s' and category='%s'" % (f,cat)).fetchone()
        if res==None: return 0
        else: return float(res[0])

    # 属于某一分类的内容项数量
    def catcount(self, cat):
        res = self.con.execute("select count from cc where \
                               category='%s'" % (cat)).fetchone()
        if res==None: return 0
        else: return float(res[0])

    # 所有内容项的数量
    def totalcount(self):
        res = self.con.execute('select sum(count) from cc').fetchone()
        if res==0 : return 0
        return res[0]


    # 所有分类的列表
    def categories(self):
        cur = self.con.execute('select category from cc')
        return [d[0] for d in cur]

    def train(self, item, cat):
        features = self.getfeatures(item)
        # 针对该分类为每个特征增加计数值
        for f in features:
            self.incf(f, cat)
        # 增加针对该分类的计数值
        self.incc(cat)

    # 计算概率
    def fprob(self, f, cat):
        if self.catcount(cat) == 0: return 0
        return self.fcount(f, cat) / self.catcount(cat)

    # 求单词在一分类中出现的概率
    # 为防止单词出现极端情形，需要根据一个假设的概率来判断（初始设置为0.5），
    # 并且为假设的概率设置权重（默认为1）即假设概率的权重与一个单词相当
    # (weight*assumedprob + count * fprob)/(count+weight)
    def weightedprob(self, f, cat, prf, weight=1.0, ap=0.5):
        # 计算当前概率值
        basicprob = prf(f, cat)

        # 统计特征在所有分类中出现的次数
        totals = sum([self.fcount(f, c) for c in self.categories()])
        # 统计特征在该分类中出现的次数
        # totals=self.fcount(f,cat)
        bp = ((weight * ap) + (totals) * basicprob) / (weight + totals)
        return bp

    # 进行样品分类
    def classify(self,item,default=None):
        probs={}
        max=0.0
        for cat in self.categories():
            probs[cat]=self.prob(item,cat)
            if max < probs[cat]:
                max=probs[cat]
                best=cat

        # 确保最大的概率值超出其他概率值*阀值
        for cat in probs:
            if cat == best : continue
            if self.getthreshold(best)*probs[cat]>max: return default

        return best

# 朴素贝叶斯分类器
class naivebayes(classifier):

    def __init__(self,getfeatures,filename=None):
        classifier.__init__(self,getfeatures,filename)
        # 设置阀值(每个分类)
        self.thresholds={}

    # 求文档的概率（文档内所有特征的概率相乘）
    # p(document|category)
    def docprob(self, item, cat):
        features = self.getfeatures(item)

        # 将所有特征的概率相乘
        p = 1
        for f in features: p *= self.weightedprob(f, cat, self.fprob)
        return p

    # 根据贝叶斯定理：p(a|b)=p(b|a)*p(a)/p(b)
    # 因此需要求出p(a)以及p(b)、即p(document)和p(category)
    # 其中p(category)为抽出文档为该分类的概率
    # p(document)为抽出一篇文档的概率，或者也可以是所有单词出现概率的乘积（假设单词间相互独立）
    def prob(self,item,cat):
        catprob = self.catcount(cat)/self.totalcount()
        docprob = self.docprob(item,cat)
        # 需要注意的是，因为最终是对所有的该结果进行比较，而p(document)又都一样，所以不需要求p(document)的值
        return docprob*catprob

    def setthreshold(self,cat,t):
        self.thresholds[cat]=t

    def getthreshold(self,cat):
        if cat not in self.thresholds: return 1.0
        return self.thresholds[cat]


class fisherclassifier(classifier):

    def __init__(self,getfeatures,filename=None):
        classifier.__init__(self,getfeatures,filename)
        # 保存分类临界值的变量
        self.minimums={}

    def setminimum(self,cat,min):
        self.minimums[cat]=min

    def getminimum(self,cat):
        if cat not in self.minimums : return 0
        return self.minimums[cat]

    # 倒置对数卡方函数
    def invchi2(self,chi,df):
        m=chi / 2.0
        sum=term=math.exp(-m)
        for i in range(1,df//2):
            term *= m/i
            sum+=term
        return min(sum,1.0)


    #求(p(category|feature方法)
    def cprob(self,f,cat):
        # 特征在该分类中出现的频率（这点待证明）
        clf=self.fprob(f,cat)
        if clf==0 :return 0.0

        #特征在所有分类出现的频率（同样有待证明）
        freqsum=sum([self.fprob(f,c) for c in self.categories()])

        return clf/(freqsum)

    # 费舍尔计算方法是将所有概率相乘起来，取自然对数，结果乘-2
    def fisherprob(self,item,cat):
        p=1
        feature=self.getfeatures(item)
        for f in feature:
            p*=(self.weightedprob(f,cat,self.cprob))

        # 取自然对数，并乘以-2
        fscore=-2*math.log(p)

        # 利用倒置对数卡方函数求得概率
        return self.invchi2(fscore,len(feature)*2)

    # 计算每个分类的概率并找到超过指定下限值的最佳结果
    def classify(self,item,default=None):
        # 循环遍历并寻找最佳结果
        best=default
        max=0.0
        for c in self.categories():
            p=self.fisherprob(item,c)
            # 确保超过其下限值
            if p>self.getminimum(c) and p>max:
                best=c
                max=p
        return best

def sampletrain(cl):
    cl.train('the quick brown fox jumps over the lazy dog', 'good')
    cl.train('make quick money in the online casino', 'bad')
    cl.train('Nobody owns the water.', 'good')
    cl.train('buy pharmaceuticals now', 'bad')
    cl.train('the quick brown fox jumps', 'good')
    cl.con.commit();



cl = fisherclassifier(getwords,'test1.db')
cl.setdb()
# sampletrain(cl)
# cl.setthreshold('bad',3.0)
# print cl.cprob('quick','good')
# print cl.weightedprob('money','bad',cl.cprob)
# print cl.fisherprob('quick rabbit','good')
# cl.setminimum('bad',0.6)
# cl.setminimum('good',0.2)
print cl.con.execute("select * from cc").fetchall()
print cl.classify('quick money')
